package simulations;

import forces.Force;
import masses.ClickAndDragMass;
import masses.Mass;
import springs.AbstractSpring;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.util.List;
import java.util.logging.Logger;


/**
 * acts as the controller in our MVC architecture
 * 
 * @author Zach Michaelov
 */
public class Controller {
    private Simulation mySimulation; // our model
    private Canvas canvas; // our view
    private static final Dimension SIZE = new Dimension(800, 800);
    private Timer myTimer;
    private static final Logger LOGGER = Logger.getLogger(Controller.class
            .getName());
    private int myLastKeyPressed;
    public static final int NO_KEY_PRESSED = -1;

    public Controller () {

        mySimulation = new Simulation(new BoundingArea(SIZE));
        canvas = new Canvas(SIZE, mySimulation);

        // create timer to drive the animation
        myTimer = new Timer(Canvas.ONE_SECOND / Canvas.FRAMES_PER_SECOND,
                new ActionListener() {
                    @Override
                    public void actionPerformed (ActionEvent e) {
                        step((double) Canvas.FRAMES_PER_SECOND
                                / Canvas.ONE_SECOND);
                    }
                });
        ClickAndDragHandler clickAndDragHandler = new ClickAndDragHandler();
        canvas.addMouseListener(clickAndDragHandler);
        canvas.addMouseMotionListener(clickAndDragHandler);
        canvas.addKeyListener(new KeyHandler());
    }

    /**
     * Starts the applet's action, i.e., starts the animation.
     */
    public void start () {
        myTimer.start();
    }

    /**
     * Stops the applet's action, i.e., the animation.
     */
    public void stop () {
        myTimer.stop();
    }

    public void toggleTimer () {
        if (myTimer.isRunning()) {
            stop();
            LOGGER.info("Timer stopped.");
        }
        else {
            start();
            LOGGER.info("Timer started.");
        }
    }

    /**
     * Take one step in the animation.
     */
    public void step (double elapsedTime) {
        // update simulation, repaint canvas
        mySimulation.update(elapsedTime);
        // indirectly causes paint to be called
        canvas.repaint();
    }

    /**
     * add an entity to our simulation
     * 
     * @param entity
     */
    public void addModel (final Entity entity) {
        mySimulation.add(entity);
    }

    public void addGlobalForces (final List<Force> forces) {
        mySimulation.add(forces);
    }

    /**
     * handles all of our keyboard interactions
     * 
     * @param keyCode
     */
    protected void handleKeyInputs (int keyCode) {
        switch (keyCode) {
            case KeyEvent.VK_SPACE:
                toggleTimer();
                break;
            case KeyEvent.VK_S:
                step((double) Canvas.FRAMES_PER_SECOND / Canvas.ONE_SECOND);
                break;
            case KeyEvent.VK_P:
                System.out.println(mySimulation);
            case KeyEvent.VK_N:
                // new model
                canvas.loadModel();
                LOGGER.info("New Entity added");
                break;
            case KeyEvent.VK_C:
                // clear all loaded assemblies
                mySimulation.clear();
                LOGGER.info("Entities cleared");
                break;
            case KeyEvent.VK_G:
                // toggle on gravity
                mySimulation.getGravity().toggleEnabled();
                LOGGER.info("Gravity enabled: "
                        + mySimulation.getGravity().isEnabled());
                break;
            case KeyEvent.VK_V:
                // toggle on viscosity
                mySimulation.getViscosity().toggleEnabled();
                LOGGER.info("Viscosity enabled: "
                        + mySimulation.getViscosity().isEnabled());
                break;
            case KeyEvent.VK_M:
                // toggle on center of mass
                mySimulation.toggleCenterOfMass();
                LOGGER.info("Center-of-Mass enabled: "
                        + mySimulation.centerOfMassEnabled());
                break;
            case KeyEvent.VK_1:
                // toggle on wall repulsion for top wall
                mySimulation.getWall(1).toggleEnabled();
                LOGGER.info("Wall 1: " + mySimulation.getWall(1).isEnabled());
                break;
            case KeyEvent.VK_2:
                // toggle on wall repulsion for right wall
                mySimulation.getWall(2).toggleEnabled();
                LOGGER.info("Wall 2: " + mySimulation.getWall(2).isEnabled());
                break;
            case KeyEvent.VK_3:
                mySimulation.getWall(3).toggleEnabled();
                LOGGER.info("Wall 3: " + mySimulation.getWall(3).isEnabled());
                // toggle on wall repulsion for bottom wall
                break;
            case KeyEvent.VK_4:
                mySimulation.getWall(4).toggleEnabled();
                LOGGER.info("Wall 4: " + mySimulation.getWall(4).isEnabled());
                // toggle on wall repulsion for left wall
                break;
            case KeyEvent.VK_UP:
                // expand window
                mySimulation.growBounds();
                LOGGER.info("Grew bounding box");
                break;
            case KeyEvent.VK_DOWN:
                // shrink window
                mySimulation.shrinkBounds();
                LOGGER.info("Shrank bounding box");
                break;
            default:
                // good style
                break;
        }
    }

    public Component getDisplay () {
        return canvas;
    }

    /**
     * Helper class for handling user interactions with the Mouse
     */
    private class ClickAndDragHandler extends MouseAdapter {
        public static final double K_VAL = 5;
        public static final int DRAG_MASS_ID = -1;

        /**
         * spring that forms the basis of our click and drag interactions
         */
        private AbstractSpring spring;
        private ClickAndDragMass startMass;
        private Entity selectedEntity;

        private ClickAndDragHandler () {
            startMass = null;
            spring = null;
            selectedEntity = null;
        }

        /**
         * instantiate
         * 
         * @param event
         */
        @Override
        public void mouseDragged (final MouseEvent event) {
            super.mouseDragged(event);
            if (startMass == null && spring == null || selectedEntity == null) {
                LOGGER.info(event.paramString()); // for debugging purposes
                Point2D mousePosition = event.getPoint();
                java.util.List<Entity> entities = mySimulation.getEntities();
                double minDistance = Double.MAX_VALUE;
                Mass closestMass = null;
                Entity closestEntity = null;
                for (Entity entity : entities) {
                    java.util.List<Mass> masses = entity.getMyMasses();
                    for (Mass mass : masses) {
                        double distance = Force.distanceBetween(mousePosition,
                                mass.getCenter());
                        if (distance < minDistance) {
                            closestMass = mass;
                            closestEntity = entity;
                            minDistance = distance;
                        }
                    }
                }
                /** the starting mass for our click and drag handler */
                startMass = new ClickAndDragMass(DRAG_MASS_ID,
                        mousePosition.getX(), mousePosition.getY(), event);
                selectedEntity = closestEntity;
                spring = new springs.Spring(startMass, closestMass,
                        minDistance, K_VAL);
                closestEntity.add(spring);
                closestEntity.add(startMass);
            }
            else {
                startMass.setEvent(event);
            }
        }

        @Override
        public void mouseReleased (final MouseEvent event) {
            super.mouseReleased(event);
            /**
             * mouse has been released, spring no longer acts on the click and
             * drag forces
             */
            LOGGER.info(event.paramString());
            // remove spring and startMass from the Entity

            selectedEntity.removeMass(DRAG_MASS_ID);
            spring = null;
            startMass = null;
        }

    }

    private class KeyHandler extends KeyAdapter {
        @Override
        public void keyPressed (KeyEvent e) {
            myLastKeyPressed = e.getKeyCode();
            handleKeyInputs(myLastKeyPressed);
        }

        @Override
        public void keyReleased (KeyEvent e) {
            myLastKeyPressed = NO_KEY_PRESSED;
        }
    }

}
